package org.spdata.tsdb.coder;

import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.spdata.tsdb.dto.DataPoint;
import org.spdata.utils.ByteUtils;
import org.spdata.utils.FloatUtils;
import org.spdata.utils.ListUtils;

/**
 * 浮点数序列编码器，要求浮点数数值波动越小压缩效率越高
 */
public class FloatArrayCoder {

	private List<Map<String,Object>> groupDeltasByLittle(List<Float> deltas){
		List<Map<String,Object>> result = new ArrayList<Map<String,Object>>();
		int i=0;
		List<Integer> list = new LinkedList<Integer>();
		for(Float delta:deltas) {			
			if(Math.abs(delta)<= 0.127) {
				list.add(i);
			}else {
				if(list.size()>4) {
					Map<String,Object> section = new HashMap<String,Object>();
					section.put("from", list.get(0));
					section.put("end", i-1);
					result.add(section);
				}
				list.clear();
			}
			i++;
		}
		if(list.size()>4) {
			Map<String,Object> section = new HashMap<String,Object>();
			section.put("from", list.get(0));
			section.put("end", i-1);
			result.add(section);
		}
		return result;
	}
	
	private List<Map<String,Object>> groupDeltasByIncrease(List<Float> deltas){
		List<Map<String,Object>> result = new ArrayList<Map<String,Object>>();
		int i=0;
		List<Integer> list = new LinkedList<Integer>();
		for(Float delta:deltas) {			
			if(delta>=0 && delta<=0.255) {
				list.add(i);
			}else {
				if(list.size()>4) {
					Map<String,Object> section = new HashMap<String,Object>();
					section.put("from", list.get(0));
					section.put("end", i-1);
					result.add(section);
				}
				list.clear();
			}
			i++;
		}
		if(list.size()>4) {
			Map<String,Object> section = new HashMap<String,Object>();
			section.put("from", list.get(0));
			section.put("end", i-1);
			result.add(section);
		}
		return result;
	}
	
	private List<Map<String,Object>> groupDeltasByDecrease(List<Float> deltas){
		List<Map<String,Object>> result = new ArrayList<Map<String,Object>>();
		int i=0;
		List<Integer> list = new LinkedList<Integer>();
		for(Float delta:deltas) {			
			if(delta>=-0.255 && delta<0 ) {
				list.add(i);
			}else {
				if(list.size()>4) {
					Map<String,Object> section = new HashMap<String,Object>();
					section.put("from", list.get(0));
					section.put("end", i-1);
					result.add(section);
				}
				list.clear();
			}
			i++;
		}
		if(list.size()>4) {
			Map<String,Object> section = new HashMap<String,Object>();
			section.put("from", list.get(0));
			section.put("end", i-1);
			result.add(section);
		}
		return result;
	}
	
	private List<Integer> groupDeltasOther(List<Float> deltas){
		List<Integer> result = new ArrayList<Integer>();
		int i=0;
		for(Float delta:deltas) {			
			if(delta<-0.255 || delta>0.255 ) {
				result.add(i);
			}
			i++;
		}
		return result;
	}
	
	private List<Map<String,Object>> groupDeltasByZero(List<Float> deltas){
		List<Map<String,Object>> result = new ArrayList<Map<String,Object>>();
		int i=0;
		List<Integer> zeroList = new LinkedList<Integer>();
		for(Float delta:deltas) {			
			if(Math.abs(delta)<0.0001) {
				zeroList.add(i);
			}else {
				if(zeroList.size()>3) {
					Map<String,Object> section = new HashMap<String,Object>();
					section.put("from", zeroList.get(0));
					section.put("end", i-1);
					result.add(section);
				}
				zeroList.clear();
			}
			i++;
		}
		return result;
	}
	
	private boolean coveredBy(Integer index ,List<Map<String,Object>> sections) {
		for(Map<String,Object> section:sections) {
			Integer from = (Integer)section.get("from");
			Integer end = (Integer)section.get("end");
			if(index>=from && index<=end) {
				return true;
			}
		}
		return false;
	}
	
	private byte changeLittleDeltaToByte(float delta) {
		int v = Math.round(delta * 1000);
		return (byte)v;
	}
	

	
	public float[] decode(byte[] input) {
		List<Float> values = new ArrayList<Float>();
		int first = ByteUtils.changeBytesToInt(input[0],input[1],input[2],input[3]);
		values.add(Float.intBitsToFloat(first));
		
		List<Float> deltas = new ArrayList<Float>();
		//decode delta=0的段落数
		int sizeOfZeroSection = 0x7F & input[4];
		int index = 5;
		for(int i=0;i<sizeOfZeroSection;i++) {
			int from = ByteUtils.changeBytesToInt(input[index],input[index+1]);
			int end = ByteUtils.changeBytesToInt(input[index+2],input[index+3]);
			ListUtils.expandList(deltas,end);
			for(int j=from;j<=end;j++) {
				deltas.set(j, 0.0f);
			}
			index+=4;
		}
		
		/*
		 * -------------------------------------------------
		 * decode abs(delta)<0.128
		 * -------------------------------------------------
		 */
		int sizeOfLittleSection = 0x7F & input[index];index++;
		for(int i=0;i<sizeOfLittleSection;i++) {
			int from = ByteUtils.changeBytesToInt(input[index],input[index+1]);
			int end = ByteUtils.changeBytesToInt(input[index+2],input[index+3]);
			index+=4;
			ListUtils.expandList(deltas,end);
			for(int j=from;j<=end;j++) {
				Float delta = deltas.get(j);
				if(delta==null) {
					int b = input[index];index++;
					delta =(0.001F*b);
					deltas.set(j, delta);
				}
			}
			
		}
		
		/*
		 * -------------------------------------------------
		 * decode 0<delta<=0.255
		 * -------------------------------------------------
		 */
		int sizeOfIncreaseSection = 0x7F & input[index];index++;
		for(int i=0;i<sizeOfIncreaseSection;i++) {
			int from = ByteUtils.changeBytesToInt(input[index],input[index+1]);
			int end = ByteUtils.changeBytesToInt(input[index+2],input[index+3]);
			index+=4;
			ListUtils.expandList(deltas,end);
			for(int j=from;j<=end;j++) {
				Float delta = deltas.get(j);
				if(delta==null) {
					int b = 0x7FFF & input[index];index++;
					delta =(0.001F*b);
					deltas.set(j, delta);
				}
			}
		}
		
		/*
		 * -------------------------------------------------
		 * decode -0.255<=delta<0
		 * -------------------------------------------------
		 */
		int sizeOfDecreaseSection = 0x7F & input[index];index++;
		for(int i=0;i<sizeOfDecreaseSection;i++) {
			int from = ByteUtils.changeBytesToInt(input[index],input[index+1]);
			int end = ByteUtils.changeBytesToInt(input[index+2],input[index+3]);
			index+=4;
			ListUtils.expandList(deltas,end);
			for(int j=from;j<=end;j++) {
				Float delta = deltas.get(j);
				if(delta==null) {
					int b =0- 0x7FFF & input[index];index++;
					delta =(0.001F*b);
					deltas.set(j, delta);
				}
			}
		}
		
		/*
		 * -------------------------------------------------
		 * decode other delta 
		 * -------------------------------------------------
		 */
		int from = 0;
		int type = input[index];index++;
		if(type==-1) {
			byte[] other = new byte[input.length-index];
			System.arraycopy(input, index, other, 0, other.length);
			FloatArrayCoder coder = new FloatArrayCoder();
			float[] otherDeltas = coder.decode(other);
			for(int i=0;i<otherDeltas.length;i++) {
				from = ListUtils.findNullAndSetValue(deltas, from, otherDeltas[i]);
			}
		}else if(type==0) {
			//do nothing
		}else if(type==2) {
			for(int i=index;i<input.length;i+=2) {
				int v = ByteUtils.changeBytesToInt(input[i],input[i+1]);
				float f = v*1.0f/1000;
				from = ListUtils.findNullAndSetValue(deltas,from+1,f);
			}
		}else if(type==3) {
			for(int i=index;i<input.length;i+=2) {
				int v = ByteUtils.changeBytesToInt(input[i],input[i+1]);
				float f = v*1.0f/100;
				from = ListUtils.findNullAndSetValue(deltas,from+1,f);
			}
		}else {
			for(int i=index;i<input.length;i+=4) {
				int v = ByteUtils.changeBytesToInt(input[i],input[i+1],input[i+2],input[i+3]);
				float f = Float.intBitsToFloat(v);
				from = ListUtils.findNullAndSetValue(deltas,from+1,f);
			}
		}
		
		//根据deltas解码
 		Float pre = values.get(0);
 		
 		for(int i=0;i<deltas.size();i++) {
 			Float delta = deltas.get(i);
 			if(delta==null)
 				System.out.println();
 			float cur = pre + delta;
 			pre = cur;
 			values.add(cur);
 		}
		
		float[] result = new float[values.size()];
		for(int i=0;i<values.size();i++) {
			result[i] =  values.get(i);
		}
		return result;
	}
	
	public byte[] encode(float[] input,boolean allowEncodeAgain) {
		ByteArrayOutputStream output = new ByteArrayOutputStream();
		//写入第一个浮点数的值
		output.write(ByteUtils.changeIntTo4Bytes(Float.floatToIntBits(input[0])),0,4);
		
		//求差值
		List<Float> deltas = new ArrayList<Float>();
		for(int i=1;i<input.length;i++) {
			deltas.add(input[i]-input[i-1]);
		}
		
		
		List<Map<String,Object>> zeroSections = this.groupDeltasByZero(deltas);
		List<Map<String,Object>> littleSections = this.groupDeltasByLittle(deltas);
		List<Map<String,Object>> increaseSections = this.groupDeltasByIncrease(deltas);
		List<Map<String,Object>> decreaseSections = this.groupDeltasByDecrease(deltas);
		List<Integer> otherSection = this.groupDeltasOther(deltas);
		
		
		/* --------------------------------------------------------
		 * 对delta=0.128的delta序列进行编码
		 * -------------------------------------------------------
		 */
		//写入有几个delta=0的段落
		output.write(zeroSections.size());
		for(Map<String,Object> section:zeroSections) {
			//写入每个段落内容
			Integer from = (Integer)section.get("from");
			Integer end = (Integer)section.get("end");
			output.write(ByteUtils.changeIntTo2Bytes(from), 0, 2);
			output.write(ByteUtils.changeIntTo2Bytes(end), 0, 2);
		}
		
		/* --------------------------------------------------------
		 * 对delta<0.128的delta序列进行编码，其中delta=0的不需要编码
		 * -------------------------------------------------------
		 */

		//去除无效的littleSections，并统计有效littleSections的数量
		Iterator<Map<String,Object>> iter = littleSections.iterator();
		while(iter.hasNext()){
			Map<String,Object> section = iter.next();
			Set<Integer> uncovered = new LinkedHashSet<Integer>();
			Integer from = (Integer) section.get("from");
			Integer end = (Integer) section.get("end");
			
			for(int i=from.intValue();i<=end.intValue();i++) {
				if(!this.coveredBy(i, zeroSections)) {
					uncovered.add(i);
				}
			}
			if(uncovered.size()>3) {
				section.put("uncovered", uncovered);
			}else {
				for(Integer index:uncovered) {
					otherSection.add(index);
				}
				iter.remove();
			}
		}
		//写入：存在几段littleSection
		output.write(littleSections.size());
		iter = littleSections.iterator();
		while(iter.hasNext()){
			Map<String,Object> section = iter.next();
			Integer from = (Integer) section.get("from");
			Integer end = (Integer) section.get("end");
			Set<Integer> uncovered = (Set<Integer>) section.get("uncovered");
			output.write(ByteUtils.changeIntTo2Bytes(from), 0, 2);
			output.write(ByteUtils.changeIntTo2Bytes(end), 0, 2);
			for(Integer index:uncovered) {
				output.write(this.changeLittleDeltaToByte(deltas.get(index)));
			}
		}
	
		
		
		/* 
		 * --------------------------------------------------------
		 * 对delta递增delta序列进行编码
		 * -------------------------------------------------------
		 */
		this.encode(deltas, increaseSections, zeroSections, littleSections, otherSection, output);
		
		/* 
		 * --------------------------------------------------------
		 * 对delta递减delta序列进行编码
		 * -------------------------------------------------------
		 */
		this.encode(deltas, decreaseSections, zeroSections, littleSections, otherSection, output);
	
		/* 
		 * --------------------------------------------------------
		 * 对其他delta进行编码
		 * -------------------------------------------------------
		 */
		//递归执行一次
		if(allowEncodeAgain) {
			if(otherSection.size()==0) {
				output.write(0);
			}else {
				float[] otherValues = new float[otherSection.size()];
				int i=0;
				for(Integer index:otherSection) {
					otherValues[i++] = deltas.get(index);
				}
				byte[] encoded = this.encode(otherValues,false);
				output.write((byte)-1);
				output.write(encoded, 0, encoded.length);
			}
		}else {
			Float max = null;
			for(Integer index:otherSection) {
				float delta = Math.abs(deltas.get(index));
				if(max==null) {
					max = delta;
				}else if(delta>max)
					max = delta;
			}
			if(max==null) { //不存在其他delta了
				output.write(0);
			}else if(max<32.767) { //用2个字节表示，保留3为小数
				output.write(2);
				for(Integer index:otherSection) {
					float delta = deltas.get(index);
					int v = Math.round(delta* 1000);
					output.write(ByteUtils.changeIntTo2Bytes(v),0,2);
				}
			}else if(max<327.67) { //用2个字节表示，保留2位小数
				output.write(3);
				for(Integer index:otherSection) {
					float delta = deltas.get(index);
					int v = Math.round(delta* 100);
					output.write(ByteUtils.changeIntTo2Bytes(v),0,2);
				}
			}else { 		//不压缩
				output.write(4);
				for(Integer index:otherSection) {
					float delta = deltas.get(index);
					output.write(ByteUtils.changeIntTo4Bytes(Float.floatToIntBits(delta)), 0, 4);
				}
			}
		}
		
		return output.toByteArray();
	}
	
	/**
	 * 对递增或递减的 delta序列进行编码
	 * @param deltas 
	 * @param zeroSections 
	 * @param littleSections
	 * @param otherSection
	 * @param output
	 */
	private void encode(List<Float> originDeltas,List<Map<String,Object>> sections,List<Map<String,Object>> zeroSections,List<Map<String,Object>> littleSections,List<Integer> otherSection,ByteArrayOutputStream output) {
		Iterator<Map<String,Object>> iter = sections.iterator();
		while(iter.hasNext()){
			Map<String,Object> section = iter.next();
			Set<Integer> uncovered = new LinkedHashSet<Integer>();
			Integer from = (Integer)section.get("from");
			Integer end = (Integer)section.get("end");
			//没有被zeroSections覆盖
			for(int i=from.intValue();i<=end.intValue();i++) {
				if(!this.coveredBy(i, zeroSections)) {
					uncovered.add(i);
				}
			}
			//也没有被littleSections覆盖
			Iterator<Integer> iter2 = uncovered.iterator();
			while(iter2.hasNext()) {
				Integer index = iter2.next();
				if(this.coveredBy(index, littleSections)) {
					iter2.remove();
				}
			}
			
			if(uncovered.size()>3) {
				section.put("uncovered", uncovered);
			}else {
				for(Integer index:uncovered) {
					otherSection.add(index);
				}
				iter.remove();
			}
		}
		
		output.write(sections.size());
		iter = sections.iterator();
		while(iter.hasNext()) {
			Map<String,Object> section = iter.next();
			Integer from = (Integer) section.get("from");
			Integer end = (Integer) section.get("end");
			Set<Integer> uncovered = (Set<Integer>) section.get("uncovered");
			output.write(ByteUtils.changeIntTo2Bytes(from), 0, 2);
			output.write(ByteUtils.changeIntTo2Bytes(end), 0, 2);
			for(Integer index:uncovered) {
				output.write(this.changeLittleDeltaToByte(originDeltas.get(index)));
			}
		}
	}
	
	public static void main(String[] args) {
		//test0();
		//test1();
		//test2();
		test3();
	}
	
	/**
	 * 对20-24之间正弦波动的浮点数序列(模拟温度变化)，测试其压缩比率
	 */
	private static void test0() {
		//生成一个浮点数序列（正弦波动，均值25，最大值27，最小值23）,并输出到控制台
		List<Float> values = new ArrayList<Float>();
		for(int i=0;i<3600;i+=1) {
			float f = FloatUtils.toShort(genValue(i,22,2),3);
			values.add(f);
		}
		float[] valArr = NumberSeqFormatter.toArray(values);
		System.out.println("原始数据,size = "+values.size()+",byte size="+(4*values.size()));
		//System.out.println(NumberSeqFormatter.toString(valArr));
		
		//编码，并输出到控制台
		FloatArrayCoder coder = new FloatArrayCoder();
		byte[] encoded = coder.encode(valArr,true);
		System.out.println("压缩数据,byte size="+encoded.length);
		//System.out.println(ByteUtils.changeBytesToHexStr(encoded));
		
		//解码，并输出到控制台
		valArr = coder.decode(encoded);
		for(int i=0;i<valArr.length;i++) {
			float f = FloatUtils.toShort(valArr[i],3);
			valArr[i]=f;
		}
		System.out.println("解压数据,size = "+values.size()+",byte size="+(4*values.size()));
		//System.out.println(NumberSeqFormatter.toString(valArr));
		
		//和原始数据比对
		int count = 0;
		for(int i=0;i<720;i++) {
			if(values.get(i)-valArr[i]>0.0001) {
				System.out.println("第"+i+"个数据不对了");
				count ++;
			}
		}
		System.out.println("共有"+count+"个数据不对");
	}
	
	/**
	 * 对200-240之间正弦波动的浮点数序列(模拟电压变化)，测试其压缩比率
	 */
	private static void test1() {
		//生成一个浮点数序列（正弦波动，均值25，最大值27，最小值23）,并输出到控制台
		List<Float> values = new ArrayList<Float>();
		for(int i=0;i<3600;i+=1) {
			float f = FloatUtils.toShort(genValue(i,220,10),3);
			values.add(f);
		}
		float[] valArr = NumberSeqFormatter.toArray(values);
		System.out.println("原始数据,size = "+values.size()+",byte size="+(4*values.size()));
		//System.out.println(NumberSeqFormatter.toString(valArr));
		
		//编码，并输出到控制台
		FloatArrayCoder coder = new FloatArrayCoder();
		byte[] encoded = coder.encode(valArr,true);
		System.out.println("压缩数据,byte size="+encoded.length);
		//System.out.println(ByteUtils.changeBytesToHexStr(encoded));
		
		//解码，并输出到控制台
		valArr = coder.decode(encoded);
		for(int i=0;i<valArr.length;i++) {
			float f = FloatUtils.toShort(valArr[i],3);
			valArr[i]=f;
		}
		System.out.println("解压数据,size = "+values.size()+",byte size="+(4*values.size()));
		//System.out.println(NumberSeqFormatter.toString(valArr));
		
		//和原始数据比对
		int count = 0;
		for(int i=0;i<720;i++) {
			if(values.get(i)-valArr[i]>0.0001) {
				System.out.println("第"+i+"个数据不对了");
				count ++;
			}
		}
		System.out.println("共有"+count+"个数据不对");
	}
	
	/**
	 * 对1-2之间正弦波动的浮点数序列(模拟能源效率参数)，测试其压缩比率
	 */
	private static void test2() {
		//生成一个浮点数序列（正弦波动，均值25，最大值27，最小值23）,并输出到控制台
		List<Float> values = new ArrayList<Float>();
		for(int i=0;i<3600;i+=1) {
			float f = FloatUtils.toShort(genValue(i,1.5f,0.5f),3);
			values.add(f);
		}
		float[] valArr = NumberSeqFormatter.toArray(values);
		System.out.println("原始数据,size = "+values.size()+",byte size="+(4*values.size()));
		//System.out.println(NumberSeqFormatter.toString(valArr));
		
		//编码，并输出到控制台
		FloatArrayCoder coder = new FloatArrayCoder();
		byte[] encoded = coder.encode(valArr,true);
		System.out.println("压缩数据,byte size="+encoded.length);
		//System.out.println(ByteUtils.changeBytesToHexStr(encoded));
		
		//解码，并输出到控制台
		valArr = coder.decode(encoded);
		for(int i=0;i<valArr.length;i++) {
			float f = FloatUtils.toShort(valArr[i],3);
			valArr[i]=f;
		}
		System.out.println("解压数据,size = "+values.size()+",byte size="+(4*values.size()));
		//System.out.println(NumberSeqFormatter.toString(valArr));
		
		//和原始数据比对
		int count = 0;
		for(int i=0;i<720;i++) {
			if(values.get(i)-valArr[i]>0.0001) {
				System.out.println("第"+i+"个数据不对了");
				count ++;
			}
		}
		System.out.println("共有"+count+"个数据不对");
	}
	
	/**
	 * 对从10000增长到10500的波动增长浮点数序列(模拟电量参数)，测试其压缩比率
	 */
	private static void test3() {
		//生成一个浮点数序列（正弦波动，均值25，最大值27，最小值23）,并输出到控制台
		float v = 10000f;
		List<Float> values = new ArrayList<Float>();
		for(int i=0;i<3600;i+=1) {
			float f = genValue(i,0.5f,0.5f);
			v+=f;
			values.add(FloatUtils.toShort(v,3));
		}
		float[] valArr = NumberSeqFormatter.toArray(values);
		System.out.println("原始数据,size = "+values.size()+",byte size="+(4*values.size()));
		//System.out.println(NumberSeqFormatter.toString(valArr));
		
		//编码，并输出到控制台
		FloatArrayCoder coder = new FloatArrayCoder();
		byte[] encoded = coder.encode(valArr,true);
		System.out.println("压缩数据,byte size="+encoded.length);
		//System.out.println(ByteUtils.changeBytesToHexStr(encoded));
		
		//解码，并输出到控制台
		valArr = coder.decode(encoded);
		for(int i=0;i<valArr.length;i++) {
			float f = FloatUtils.toShort(valArr[i],3);
			valArr[i]=f;
		}
		System.out.println("解压数据,size = "+values.size()+",byte size="+(4*values.size()));
		//System.out.println(NumberSeqFormatter.toString(valArr));
		
		//和原始数据比对
		int count = 0;
		for(int i=0;i<720;i++) {
			if(values.get(i)-valArr[i]>0.0001) {
				System.out.println("第"+i+"个数据不对了");
				count ++;
			}
		}
		System.out.println("共有"+count+"个数据不对");
	}
	
	private static float genValue(long time,float avg,float off){
		double d = 1.0 * time % (20*60) / (20*60);
		double arc = 2* Math.PI * d % (2*Math.PI);
		double value = avg + off * Math.sin(arc)/2;
		return (float)value;
	}
}
